iT邦幫忙

2021 iThome 鐵人賽

DAY 12
1

Keyword: swift,swiftUI,ObservableObject
到Day12 使用swiftUI顯示Ktor的資料 放在這邊
KMMDay12


昨天用了Android顯示資料,今天就來用iOS顯示吧.今天大部分的時間都會在Xcode中進行.

開始撰寫swift

首先...先在gradle(shared)內的iOS區添加Ktor的依賴,這是給iOS所使用的Ktor,加入後記得sync Gradle啊

...//gradle中
val iosMain by getting{
    dependencies{
    implementation(Develop.Ktor.ios)
    }
 }

...//Dependencies中
object Ktor{
      ...
      val ios = "io.ktor:ktor-client-ios:${Versions.ktor}"
      ...
}

https://github.com/officeyuli/itHome2021/raw/main/day12/xcodeLocation.jpg

然後找到Android Studio 中找到Xcode專案的位置,如果沒有更改的話應該會在iosApp package底下,一個副檔名為.xcodeproj的檔案

在上面右鍵⇒ Open in ⇒ Xcode 使用Xcode打開.

原本iOS所使用的swift語言,並不能支持Kotlin獨特的suspend與coroutine機制,但好在經過KMM中間層的處理,讓這段東西下放到Kotlin原生來處理.

由於網路請求是一個耗時工作,現在我們要來實作一個可供觀察的資料來源,讓網路請求的結果被通知,在Android中昨天我們已經使用了LiveData來處理,而在iOS中,則是可以使用ObservableObject來作為資料來源.

很巧的是,這兩種解法殊途同歸,都是利用了觀察者模式.來降低資料層與畫面層之間的耦合關係.之後可以研究這兩段的程式碼,資料層的部分都是不知道自己是被如何使用.只盡責地在資料變化時發布改變的消息出去,而接收到的畫面層根據自己的需要來顯示.這點不只是在Android或是iOS,在KMM中也是隨處可見.

建立ObservableObject提供資料

(今天的以下部分都將在Xcode上撰寫iOS專案,使用swift)

我們在iosApp資料夾上右鍵,打開選單,選擇New File

https://github.com/officeyuli/itHome2021/raw/main/day12/new%20file.png

然後建立一個新的swift檔案

https://github.com/officeyuli/itHome2021/raw/main/day12/createNewFile.jpg

命名用被觀察的物件加上ViewModel字尾,以這次的CafeResponseItem為例,新的檔案名稱為"CafeResponseItemViewModel"

建立一個CafeResponseItemViewModel的物件 ,繼承ObservableObject,並且把KMM的shared import進來.

import Foundation
import shared

class CafeResponseItemViewModel: ObservableObject {
   
}

(其實Swift寫起來跟Kotlin很類似呢,所以 Kotlin經驗者在閱讀swift的語法上應該沒什麼問題)

把來自shared的DataRepository加入,注意swift和kotlin相比,多了一個let的關鍵字,而self接近Kotlin的this關鍵字

class CafeResponseItemViewModel: ObservableObject {

    private let repository: DataRepository
    
    init(repository: DataRepository) {
        self.repository = repository
    }
}

這樣我們就能夠在其中使用DataRepository了

然後把我們回傳的CafeResponseItem Array標記為@Published ,告訴其他觀察者這邊會發送資訊,請記得接收.

最後加上一個拉取資料的方法,讓CafeResponseItem的Array藉由Ktor去更新,整個class如下:

import Foundation
import shared

class CafeResponseItemViewModel: ObservableObject {
    @Published var cafeResponseItemList = [CafeResponseItem]()

    private let repository: DataRepository
    
    init(repository: DataRepository) {
        self.repository = repository
    }
    
    func fetch() {
        repository.fetchCafesFromNetwork(cityName:"taipei"){ result , error in
            if let result = result{
                self.cafeResponseItemList = result
            }
        }
    }
}

將資料顯示在iOS畫面上

回到KMM專案一開始就建立好的ContentView.swift,這邊是使用swiftUI的格式,也就是所謂的用宣告式UI,最近越來越流行了,在Flutter與Android compose都能看到類似的組件.

import SwiftUI
import shared

struct ContentView: View {
	let greet = Greeting().greeting()

	var body: some View {
		Text(greet)
	}
}

struct ContentView_Previews: PreviewProvider {
	static var previews: some View {
		ContentView()
	}
}

PreviewProvider只是提供開發者的預覽功能,真正的View是在ContentView.

先把範例預先建立好的greet與Text都刪除,我們不需要了,然後放上我們剛剛建立好的CafeResponseItemViewModel

這邊要標記@ObservedObject,與剛剛的@Published對應,說明這是接收訂閱的一方.

struct ContentView: View {
	@ObservedObject var cafeResponseItemViewModel =   CafeResponseItemViewModel(repository:DataRepository())
	var body: some View {
	}
}

body的內容雖然可以直接把觀察到的資料放進去,但是iOS有提供相當美觀的NavigationView功能,可以提高使用者的體驗,我們來試用看看.

這邊先用一個簡單的文字來看看Navigation效果,將body的View的內容加入NavigationView

var body: some View {
        NavigationView {
            Text("Hi")
            .navigationBarTitle(Text("CafeList"), displayMode: .large)
        }
    }

然後在View的onAppear,由cafeResponseItemViewModel進行網路請求,這是在View出現時會執行其中的內容

var body: some View {
        NavigationView {
            Text("Hi")
            .navigationBarTitle(Text("CafeList"), displayMode: .large)
            .onAppear(perform: {
            self.cafeResponseItemViewModel.fetch()
            })
        }
    }

使用List ,將請求回傳的List一一顯示

var body: some View {
        NavigationView {
            List(cafeResponseItemViewModel.cafeResponseItemList, id: \.id) { cafe in
                Text(cafe.name)
            }
            .navigationBarTitle(Text("CafeList"), displayMode: .large)
            .onAppear(perform: {
                self.cafeResponseItemViewModel.fetch()
            })
        }
    }

整體會像這樣

https://github.com/officeyuli/itHome2021/raw/main/day12/NameList.jpg

最後再把通用文字View替換成自定義的CafeItemView,整個ContentView.swift如下

import shared

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
struct ContentView: View {
    @ObservedObject var cafeResponseItemViewModel =  CafeResponseItemViewModel(repository:DataRepository())
    var body: some View {
        NavigationView {
            List(cafeResponseItemViewModel.cafeResponseItemList, id: \.id) { cafe in
                CafeItemView(cafeResponseItem: cafe)
            }
            .navigationBarTitle(Text("CafeList"), displayMode: .large)
            .onAppear(perform: {
                self.cafeResponseItemViewModel.fetch()
            })
        }
    }
}

struct CafeItemView : View {
    var cafeResponseItem: CafeResponseItem

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(cafeResponseItem.name).font(.headline)
                Text(cafeResponseItem.id).font(.subheadline)
            }
        }
    }
}

跑起來看看

https://github.com/officeyuli/itHome2021/raw/main/day12/CafeList.jpg

Ktor的資料成功顯示在iOS畫面上了!

明天將會研究,如果有各平台不同的實作方法,例如最常用的Log在iOS與Android就不相同,這種情況應該如何去撰寫


上一篇
Day 11: 回到原生環境!在Android上展示Ktor資料!
下一篇
Day 13:因應在地口味調整,根據各平台實作功能!
系列文
挑戰 Kotlin Multiplatform Mobile 跨平台開發,透過共同的Kotlin模組同時打造iOS與Android應用!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言